var lengthOfLongestSubstring = function(s) {
    // 1. 初始化一个 Set 数据结构，用于存储当前窗口中已经出现过的字符。
    //    它就像一个哈希表，能让我们在 O(1) 的时间内判断一个字符是否存在。
    const occ = new Set();
    
    // 2. 获取字符串的长度，用于边界判断。
    const n = s.length;
    
    // 3. 初始化右指针 rk (right key)，初始值为 -1。
    //    这是一个很巧妙的初始化方式，表示右指针一开始在字符串的左边界之外，
    //    意味着窗口 [i, rk] 在循环开始前是一个空窗口。
    // 4. 初始化答案 ans，用于记录最长子串的长度，初始值为 0。
    let rk = -1, ans = 0;
    
    // 5. 遍历字符串，这里的循环变量 i 可以看作是滑动窗口的左指针。
    //    i 从 0 开始，代表我们要探索以 s[i] 为起始字符的最长无重复子串。
    for (let i = 0; i < n; ++i) {
        
        // 6. 这是算法的关键步骤之一。
        //    当 i 不等于 0 时，意味着我们正在开始探索一个新的窗口（左指针向右移动了）。
        //    因此，我们需要将上一个窗口的最左侧字符 s[i-1] 从 Set 中删除，
        //    以保证 Set 中只包含当前窗口 [i, rk] 内的字符。
        if (i != 0) {
            occ.delete(s.charAt(i - 1));
        }
        
        // 7. 这是一个 while 循环，负责尽可能地向右扩展窗口。
        //    循环条件：
        //    a. rk + 1 < n：确保下一个要考察的字符索引不越界。
        //    b. !occ.has(s.charAt(rk + 1))：检查下一个字符（rk+1位置）是否已经在当前窗口（Set）中。
        //    如果两个条件都满足，说明我们可以安全地将右指针向右移动一位。
        while (rk + 1 < n && !occ.has(s.charAt(rk + 1))) {
            
            // 8. 将这个新的、不重复的字符添加到 Set 中，表示它已经进入了窗口。
            occ.add(s.charAt(rk + 1));
            
            // 9. 右指针 rk 向右移动一位，窗口成功扩大。
            ++rk;
        }
        
        // 10. 当 while 循环结束时，窗口 [i, rk] 已经是一个以 s[i] 为起点的、最长的无重复子串。
        //     此时，我们计算这个窗口的长度 (rk - i + 1)，
        //     并与当前记录的最长长度 ans 进行比较，取较大的值更新 ans。
        ans = Math.max(ans, rk - i + 1);
    }
    
    // 11. 遍历结束后，ans 中存储的就是整个字符串中最长无重复子串的长度。
    return ans;
};